/****************************************************************************
 **
 ** This file is part of the yFiles extension package yBPMN-1.0.
 ** 
 ** yWorks proprietary/confidential. Use is subject to license terms.
 **
 ** Redistribution of this file or of an unauthorized byte-code version
 ** of this file is strictly forbidden.
 **
 ** Copyright (c) 2012 by yWorks GmbH, Vor dem Kreuzberg 28, 
 ** 72070 Tuebingen, Germany. All rights reserved.
 **
 ***************************************************************************/
package com.opNaga.bpmn;

import com.yworks.yfiles.bpmn.layout.BpmnElementTypes;
import com.yworks.yfiles.bpmn.layout.BpmnLayouter;
import com.yworks.yfiles.bpmn.view.BpmnRealizerFactory;
import com.yworks.yfiles.bpmn.view.BpmnTypeEnum;
import y.base.*;
import y.geom.YDimension;
import y.layout.EdgeLabelLayout;
import y.layout.LayoutGraph;
import y.layout.Layouter;
import y.module.LayoutModule;
import y.option.OptionHandler;
import y.util.DataProviders;
import y.view.Graph2D;
import y.view.Selections;
import y.view.tabular.TableGroupNodeRealizer;

class BpmnEdLayoutModule extends LayoutModule {
  static final int LAYOUT_FULL = 0;
  static final int LAYOUT_SELECTED_EDGES = 1;
  static final int LAYOUT_INCREMENTAL = 2;
  static final int LAYOUT_SKETCH = 3;

  private static final String BPMN_LAYOUTER = "BPMN Layouter";  
  private static final String MINIMUM_NODE_DISTANCE = "Minimum Node Distance";
  private static final String MINIMUM_EDGE_DISTANCE = "Minimum Edge Distance";
  private static final String MINIMUM_POOL_DISTANCE = "Minimum Pool Distance";
  private static final String LANE_BORDER_INSET = "Lane Border Inset";

  private static final String ORIENTATION = "Orientation";
  private static final String LEFT_TO_RIGHT = "Left to Right";
  private static final String TOP_TO_BOTTOM = "Top to Bottom";
  private static final String AUTOMATIC = "Automatic";

  private static final Object NODES_KEY = BpmnLayouter.SPHERE_OF_ACTION_NODES_DPKEY;
  private static final Object EDGES_KEY = BpmnLayouter.SPHERE_OF_ACTION_EDGES_DPKEY;

  private static final String[] orientEnum = {
      AUTOMATIC,
      LEFT_TO_RIGHT,
      TOP_TO_BOTTOM
  };


  private int usedLayouter = LAYOUT_FULL;
  private DataProvider selectedNodesBkp = null;
  private DataProvider selectedEdgesBkp = null;

  public BpmnEdLayoutModule() {
    super(BPMN_LAYOUTER, "yFiles Layout Team", "Bpmn Layouter");
  }

  public OptionHandler createOptionHandler() {
    OptionHandler op = new OptionHandler(getModuleName());
    op.addEnum(ORIENTATION, orientEnum, 0);
    op.addDouble(MINIMUM_NODE_DISTANCE, 30);
    op.addDouble(MINIMUM_EDGE_DISTANCE, 20);
    op.addDouble(MINIMUM_POOL_DISTANCE, 20);
    op.addDouble(LANE_BORDER_INSET, 20);
    return op;
  }

  public void start(Graph2D graph, int usedLayouter) {
    this.usedLayouter = usedLayouter;
    super.start(graph);
  }

  public void mainrun() {
    Graph2D graph = getGraph2D();

    selectedNodesBkp = graph.getDataProvider(NODES_KEY);
    selectedEdgesBkp = graph.getDataProvider(EDGES_KEY);

    BpmnLayouter layouter = (BpmnLayouter) createLayouter(graph);
    boolean horizontalLayout = (layouter.getLayoutOrientation() == BpmnLayouter.ORIENTATION_LEFT_TO_RIGHT);
    getLayoutExecutor().getTableLayoutConfigurator().setHorizontalLayoutConfiguration(horizontalLayout);
    getLayoutExecutor().getTableLayoutConfigurator().setTableToTableDistance(layouter.getPoolDistance());
    getLayoutExecutor().getTableLayoutConfigurator().setFromSketchModeEnabled(true);
    getLayoutExecutor().setConfiguringTableNodeRealizers(true);

    try {
      launchLayouter(layouter);
    } finally {
      dispose(graph);
    }
  }

  public Layouter createLayouter(Graph2D graph) {
    final OptionHandler op = getOptionHandler();

    BpmnLayouter layouter = new BpmnLayouter();

    if(usedLayouter == LAYOUT_SKETCH) {
      layouter.setLayoutMode(BpmnLayouter.MODE_FULL_LAYOUT);
      layouter.setSphereOfAction(BpmnLayouter.LAYOUT_SELECTED_ELEMENTS);
      graph.addDataProvider(NODES_KEY, DataProviders.createConstantDataProvider(Boolean.FALSE));
      graph.addDataProvider(EDGES_KEY, DataProviders.createConstantDataProvider(Boolean.FALSE));
    } else if(usedLayouter == LAYOUT_INCREMENTAL) {
      layouter.setLayoutMode(BpmnLayouter.MODE_FULL_LAYOUT);
      layouter.setSphereOfAction(BpmnLayouter.LAYOUT_SELECTED_ELEMENTS);
      graph.addDataProvider(NODES_KEY, Selections.createSelectionNodeMap(graph));
      graph.addDataProvider(EDGES_KEY, Selections.createSelectionEdgeMap(graph));
    } else if(usedLayouter == LAYOUT_SELECTED_EDGES) {
      layouter.setLayoutMode(BpmnLayouter.MODE_ROUTE_EDGES);
      layouter.setSphereOfAction(BpmnLayouter.LAYOUT_SELECTED_ELEMENTS);
      graph.addDataProvider(NODES_KEY, Selections.createSelectionNodeMap(graph));
      graph.addDataProvider(EDGES_KEY, Selections.createSelectionEdgeMap(graph));
    } else {
      layouter.setLayoutMode(BpmnLayouter.MODE_FULL_LAYOUT);
      layouter.setSphereOfAction(BpmnLayouter.LAYOUT_ALL_ELEMENTS);
    }

    layouter.setMinimumNodeDistance(op.getDouble(MINIMUM_NODE_DISTANCE));
    layouter.setMinimumEdgeLength(op.getDouble(MINIMUM_EDGE_DISTANCE));
    layouter.setLaneInsets(op.getDouble(LANE_BORDER_INSET));

    if (op.get(ORIENTATION).equals(AUTOMATIC)) {
      layouter.setLayoutOrientation(BpmnLayouter.ORIENTATION_LEFT_TO_RIGHT); //default
      
      for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
        Node n = nc.node();
        if(graph.getRealizer(n) instanceof TableGroupNodeRealizer) {
          BpmnTypeEnum poolType = BpmnRealizerFactory.getType(graph.getRealizer(n));
          if(BpmnTypeEnum.POOL_TYPE_COLUMN.equals(poolType)) {
            layouter.setLayoutOrientation(BpmnLayouter.ORIENTATION_TOP_TO_BOTTOM);
          } else {
            layouter.setLayoutOrientation(BpmnLayouter.ORIENTATION_LEFT_TO_RIGHT);
          }
          break;
        }
      }
    } else if (op.get(ORIENTATION).equals(LEFT_TO_RIGHT)) {
      layouter.setLayoutOrientation(BpmnLayouter.ORIENTATION_LEFT_TO_RIGHT);
    } else {
      layouter.setLayoutOrientation(BpmnLayouter.ORIENTATION_TOP_TO_BOTTOM);
    }

    double minimumPoolDistance = calcPoolDistance(graph, op.getDouble(MINIMUM_POOL_DISTANCE),
        layouter.getLayoutOrientation() == BpmnLayouter.ORIENTATION_LEFT_TO_RIGHT);
    layouter.setPoolDistance(minimumPoolDistance);

    return layouter;
  }

  private static final double MIN_LABEL_TO_LABEL_DISTANCE = 5;

  private double calcPoolDistance(LayoutGraph g, double minimumPoolDistance, boolean horizontalOrientation) {
    double poolDistance = minimumPoolDistance;
    for (EdgeCursor ec = g.edges(); ec.ok(); ec.next()) {
      Edge e = ec.edge();
      int eType = getType(e, g);
      if (eType == BpmnElementTypes.EDGE_TYPE_MESSAGE_FLOW) {
        //labels of message flow elements should always fit into the empty space between different pools
        double labelLength = 0;
        EdgeLabelLayout[] ell = g.getEdgeLabelLayout(e);
        for (int i = 0; i < ell.length; i++) {
          EdgeLabelLayout labelLayout = ell[i];
          YDimension labelSize = labelLayout.getBox();
          if (horizontalOrientation) {
            labelLength += labelSize.getHeight();
          } else {
            labelLength += labelSize.getWidth();
          }
          if (i > 0) {
            labelLength += MIN_LABEL_TO_LABEL_DISTANCE;
          }
        }
        poolDistance = Math.max(poolDistance, labelLength);
      }
    }

    return poolDistance;
  }

  private static int getType(Edge e, Graph graph) {
    DataProvider edge2Type = graph.getDataProvider(BpmnLayouter.BPMN_EDGE_TYPE_DPKEY);
    if (edge2Type == null) {
      return BpmnElementTypes.TYPE_INVALID;
    } else {
      return edge2Type.getInt(e);
    }
  }

  public void dispose(Graph2D graph) {
    graph.removeDataProvider(NODES_KEY);
    if (selectedNodesBkp != null) {
      graph.addDataProvider(NODES_KEY, selectedNodesBkp);
      selectedNodesBkp = null;
    }
    graph.removeDataProvider(EDGES_KEY);
    if (selectedEdgesBkp != null) {
      graph.addDataProvider(EDGES_KEY, selectedEdgesBkp);
      selectedEdgesBkp = null;
    }
  }
}
